﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.IO.Compression;
using System.Xml.XPath;
using Microsoft.SmartDevice.Connectivity;
using Ionic.Zip;
using System.Xml;
using Manifest;
using System.Diagnostics;
using System.Threading.Tasks;

namespace WindowsPhone.Tools
{
    public class Xap
    {
        /// <summary>
        /// The file that this class is pointing to.
        /// Cannot be changed (set once via the constructor), create a new object
        /// if you want to point to a different file.
        /// </summary>
        /// 


        #region Vars
        string ManifestPath = @Environment.GetEnvironmentVariable("TEMP").ToString() + "\\Ezdeploy\\";
        string WMAppManifest = "WMAppManifest.xml";
        string singcodePath = @"SignTools";
        string DllTempPath = @Environment.GetEnvironmentVariable("TEMP").ToString() + "\\Ezdeploy\\";
        public string willSetAppName { get; set; }
        public string willSetAuthor{get;set;}
        public string willSetDes{get;set;}
        public string willSetSubTitle { get; set; }
        public bool isWillSetIntoSettings {get;set;}
        public bool isWillSetIntoResumalbe{get;set;}
        public bool IsInfoWillUpdate { get; set; }
        public bool IsIconWillUpdate { get; set; }
        private bool _isIconUpdated;
        public bool IsIconUpdated
        {
            get
            {
                return _isIconUpdated;
            }
            set
            {
                _isIconUpdated = value;
            }
        }
        private bool _isXapInfoUpdated = false;

        private string _icon;
        public string Icon
        {
            get
            {
                if (_icon == null)
                    InitFromManifest();

                return _icon;
            }
            private set { _icon = value; }
        }

        private Stream _iconPath;
        public Stream IconPath
        {
            get
            {
                if (_iconPath == null)
                    InitFromManifest();

                return _iconPath;
            }
            private set { _iconPath = value; }
        }

        private string _newIconPath;
        public string NewIconPath
        {
            get
            {
                if (_newIconPath == null)
                {
                    return _icon;
                }
                return _newIconPath;
            }
            set
            {
                if (value != null)
                {
                    IsIconUpdated = true;
                }
                else
                {
                    IsIconUpdated = false;
                }
                _newIconPath = value;
            }
        }

        public bool IsBgIconWillUpdate { get; set; }

        private string _bgIcon;
        public string BgIcon
        {
            get
            {
                if (_bgIcon == null)
                    InitFromManifest();

                return _bgIcon;
            }
            private set { _bgIcon = value; }
        }

        private bool _isBgIconUpdated;
        public bool IsBgIconUpdated
        {
            get
            {
                return _isBgIconUpdated;
            }
            set
            {
                _isBgIconUpdated = value;
            }
        }

        private Stream _bgIconPath;
        public Stream BgIconPath
        {
            get
            {
                if (_bgIconPath == null)
                    InitFromManifest();

                return _bgIconPath;
            }
            private set { _bgIconPath = value; }
        }

        private string _newBgIconPath;
        public string NewBgIconPath
        {
            get
            {
                if (_newBgIconPath == null)
                {
                    return _icon;
                }
                return _newBgIconPath;
            }
            set
            {
                if (value != null)
                {
                    IsBgIconUpdated = true;
                }
                else
                {
                    IsBgIconUpdated = false;
                }
                _newBgIconPath = value;
            }
        }

        public bool IsInSetting { get; private set; }
        public bool IsResumable { get; private set; }

        public string FilePath_bak { get; private set; }
        public string FilePath { get; private set; }

        public string FileName { get; private set; }


        private string _version;
        public string Version
        {
            get
            {
                if (_version == null)
                    InitFromManifest();

                return _version;
            }
            private set { _version = value; }
        }

        private Guid _guid = Guid.Empty;
        public Guid Guid
        {
            get
            {
                if (_guid == Guid.Empty)
                    InitFromManifest();

                return _guid;
            }
            private set { _guid = value; }
        }

        private string _author;
        public string Author
        {
            get
            {
                if (_author == null)
                    InitFromManifest();

                return _author;
            }
            private set { _author = value; }
        }

        private string _name;
        public string Name
        {
            get {
                if (_name == null)
                    InitFromManifest();

                return _name; 
            }
            private set { _name = value; }
        }

        private string _description;
        public string Description
        {
            get
            {
                if (_description == null)
                    InitFromManifest();

                return _description;
            }
            private set { _description = value; }
        }

        private string _subTitle;
        public string SubTitle
        {
            get
            {
                if (_subTitle == null)
                    InitFromManifest();

                return _subTitle;
            }
            private set { _subTitle = value; }
        }

        #endregion

        public Xap(string file)
        {
            if (!File.Exists(file))
                throw new FileNotFoundException(file);

            FilePath = file;
            FilePath_bak = FilePath;
            FileName = FileOpration.GetFileNameFromPath(FilePath);
            InitFromManifest();
        }

        #region Sign

        public void ExtractDlls(string outPutPath)
        {
            ZipFile zip = new ZipFile(@FilePath, System.Text.Encoding.Default);
            List<ZipEntry> entries = new List<ZipEntry>();
            foreach (ZipEntry entry in zip)
            {
                if (entry.FileName.Contains(".dll"))
                {
                    entry.Extract(outPutPath, ExtractExistingFileAction.OverwriteSilently);
                }
            }
        }

        public void SignOneDllFile(string dllFileName)
        {
            try
            {
                string arg = "-spc pubkeycert.cer -v privkey.pvk " + "\"" + dllFileName + "\"";
                Process p = new Process()
                {
                    StartInfo =
                    {
                        //非常重要的一项，WorkingDirectory，如果不设置，就会出现 Unable to open CSP 的错误！
                        WorkingDirectory = singcodePath,
                        FileName = singcodePath + @"\signcode.exe",
                        Arguments = arg,
                        UseShellExecute = false,
                        CreateNoWindow = true,
                    }
                };
                p.Start();
                p.WaitForExit();
            }
            catch (Exception ex)
            {
                throw ex;
            }  
        }

        public List<string> SignDllsInDictionary(string dllFileDictionary)
        {
            List<string> signedDllsPath = new List<string>();
            foreach (string filePath in System.IO.Directory.GetFiles(dllFileDictionary))
            {
                SignOneDllFile(filePath);
                signedDllsPath.Add(filePath);
            }
            return signedDllsPath;
        }

        public void AddDllsInToXap(ZipFile zip,List<string> dllsPath)
        {
            try
            {
                zip.UpdateFiles(dllsPath, "");
            }
            catch (Exception ex)
            {
                throw ex;
            } 
        }

        public void RemovePRHeader(ZipFile zip)
        {
            try
            {
                foreach (ZipEntry entry in zip)
                {
                    if (entry.FileName == "WMAppPRHeader.xml")
                    {
                        zip.RemoveEntry(entry);
                        break;
                    }
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            
        }

        public void SignXap(ZipFile zip)
        {
            ExtractDlls(DllTempPath);
            List<string> signedDlls = SignDllsInDictionary(DllTempPath);
            AddDllsInToXap(zip, signedDlls);
            RemovePRHeader(zip);
        }

        public MemoryStream UpdateFileInXap(string path)
        {
            MemoryStream rv = new MemoryStream(2048);
            using (ZipFile zip = ZipFile.Read(@FilePath))
            {
                //更新Manifest.xml
                zip.UpdateFile(path + WMAppManifest, "");

                //更新小图标
                if (IsIconUpdated && IsIconWillUpdate)
                {
                    zip.UpdateFile(NewIconPath, "").FileName = Icon;
                    _isXapInfoUpdated = true;
                }

                //更新大图标
                if (IsBgIconUpdated && IsBgIconWillUpdate)
                {
                    zip.UpdateFile(NewBgIconPath, "").FileName = BgIcon;
                    _isXapInfoUpdated = true;
                }

                //重新签名以破解xap
                SignXap(zip);

                //将源文件的目录备份
                FilePath_bak = FilePath;

                //将更新后的文件重新命名，加上-Mod
                FilePath = FilePath.Replace(".xap", "-Mod.xap");

                //将zip文件保存到MemoryStrean rv中
                zip.Save(rv);

                //删除Dll临时存放目录
                Directory.Delete(DllTempPath, true);
            }
            rv.Seek(0, SeekOrigin.Begin);
            return rv;
        }

        public void SaveAsToXapFile(string path)
        {
            //读取更新过的zip文件流
            MemoryStream rv = UpdateFileInXap(path);

            //保存zap
            using (ZipFile zip1 = ZipFile.Read(rv))
            {
                //如果该文件存在，则删除
                if (File.Exists(FilePath))
                {
                    File.Delete(FilePath);
                }
                zip1.Save(FilePath);
            }
        }

        #endregion

        #region Manifest

        public void ExtractWMAppManifest(string outPutPath)
        {
            ZipFile zip = new ZipFile(@FilePath, System.Text.Encoding.Default);
            List<ZipEntry> entries = new List<ZipEntry>();
            foreach (ZipEntry entry in zip)
            {
                if (entry.FileName == "WMAppManifest.xml")
                {
                    //rv = new MemoryStream(2048);
                    entry.Extract(outPutPath, ExtractExistingFileAction.OverwriteSilently);
                    //rv.Seek(0, SeekOrigin.Begin);
                    break;
                }
            }
        }

        private XPathNavigator CreateNavigator(Stream manifestStream)
        {
            XPathDocument document = new XPathDocument(manifestStream);
            XPathNavigator navigator = document.CreateNavigator().SelectSingleNode("//App");
            return navigator;
        }

        #region Customization
        public void MakeAppIntoSettings(XmlDocument document)
        {
            ManifestHelper.CreateAttributeInApp(document, "HubType", "268435456");
            IsInSetting = true;
        }

        public void DisMakeAppIntoSettings(XmlDocument document)
        {
            ManifestHelper.DeleteAttribueAnyWhereUnderApp(document, "/App", "HubType");
            IsInSetting = false;
        }

        public void DisMakeAppIntoResumable(XmlDocument document)
        {
            ManifestHelper.DeleteAttribueAnyWhereUnderApp(document, "/App/Tasks/DefaultTask", "ActivationPolicy");
            IsResumable = false;
        }

        public void MakeAppIntoResumable(XmlDocument document)
        {
            ManifestHelper.CreateAttribueAnyWhereUnderApp(document, "Tasks/DefaultTask", "ActivationPolicy", "Resume");
            IsResumable = true;
        }

        public void ChangeDescription(XmlDocument document, string value)
        {
            ManifestHelper.ChangeAttributeValue(document, "", "Description", value);
        }

        public void ChangePublisher(XmlDocument document, string value)
        {
            ManifestHelper.ChangeAttributeValue(document, "", "Publisher", value);
        }

        public void ChangeTitle(XmlDocument document, string value)
        {
            ManifestHelper.ChangeAttributeValue(document, "", "Title", value);
        }

        public void ChangeAuthor(XmlDocument document, string value)
        {
            ManifestHelper.ChangeAttributeValue(document, "", "Author", value);
        }

        public void ChangeSubTitle(XmlDocument document, string value)
        {
            ManifestHelper.ChangNodeValue(document, "//Title", value);
        }
        #endregion
   

        #region GetInfomation

        private string GetAppNameFromManifestStream(XPathNavigator navigator)
        {
            string path = "App";
            NavigatorHelper.MoveNavigatorToPath(navigator, path); 
            string temp = navigator.GetAttribute("Title", string.Empty);
            if (temp == "@AppResLib.dll,-100" || temp == "")
            {
                return FileName;
            }
            else
            {
                return temp;
            }
        }

        private string GetAppVersionFromNavigator(XPathNavigator navigator)
        {
            string path = "App";
            NavigatorHelper.MoveNavigatorToPath(navigator, path); 
            return navigator.GetAttribute("Version", string.Empty);
        }

        private string GetAppAuthorFromNavigator(XPathNavigator navigator)
        {
            string path = "App";
            NavigatorHelper.MoveNavigatorToPath(navigator, path); 
            return navigator.GetAttribute("Author", string.Empty);
        }

        private Guid GetAppGuidFromNavigator(XPathNavigator navigator)
        {
            string path = "App";
            NavigatorHelper.MoveNavigatorToPath(navigator, path); 
            return new Guid(navigator.GetAttribute("ProductID", string.Empty));
        }

        private string GetAppDesFromNavigator(XPathNavigator navigator)
        {
            string path = "App";
            NavigatorHelper.MoveNavigatorToPath(navigator, path); 
            return navigator.GetAttribute("Description", string.Empty);
        }

        private bool GetAppIsInSettingFromNavigator(XPathNavigator navigator)
        {
            string path = "App";
            NavigatorHelper.MoveNavigatorToPath(navigator, path); 
            string hubstring = navigator.GetAttribute("HubType", string.Empty);
            if (hubstring == "268435456")
            {
                 return true;
            }
            else
            {
                return false;
            }
        }

        private bool GetAppIsResumableFromNavigator(XPathNavigator navigator)
        {
            string path = "App/Tasks/DefaultTask";
            NavigatorHelper.MoveNavigatorToPath(navigator, path); 
            if (navigator.GetAttribute("ActivationPolicy", string.Empty) == "Resume")
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        private string GetAppIconFromNavigator(XPathNavigator navigator)
        {
            string path = "App/IconPath";
            NavigatorHelper.MoveNavigatorToPath(navigator, path);
            return navigator.Value;
        }

        private string GetAppBgIconFromNavigator(XPathNavigator navigator)
        {
            string path="App/Tokens/PrimaryToken/TemplateType5/BackgroundImageURI";
            NavigatorHelper.MoveNavigatorToPath(navigator,path);
            return navigator.Value;
        }

        private string GetAppSubTitleFromNavigator(XPathNavigator navigator)
        {
            string path = "App/Tokens/PrimaryToken/TemplateType5/Title";
            string alterpath = "App/Tokens/PrimaryToken/TemplateType6/Title";
            NavigatorHelper.MoveNavigatorToPath(navigator, path, alterpath);
            return navigator.Value; ;
        }

        private void InitFromManifest()
        {
            using (Stream manifestStream = GetFileStreamFromXap("WMAppManifest.xml"))
            {
                XPathNavigator navigator = CreateNavigator(manifestStream);
                GetBasicInfo(navigator);
                GetMoreInfo(navigator);
                AddTopersistedAppData();
                DllTempPath += (Name + Version + @"\Dll\");
            }
        }

        private void GetBasicInfo(XPathNavigator navigator)
        {
            if (_guid == Guid.Empty)
            {
                Guid = GetAppGuidFromNavigator(navigator);
            }

            if (_name == null)
            {
                Name = GetAppNameFromManifestStream(navigator);
            }

            if (_version == null)
            {
                Version = GetAppVersionFromNavigator(navigator);
            }

            IsInSetting = GetAppIsInSettingFromNavigator(navigator);
            isWillSetIntoSettings = IsInSetting;

            if (_icon == null)
            {
                Icon = GetAppIconFromNavigator(navigator);
                IconPath = GetFileStreamFromXap(Icon);
            }

            IsResumable = GetAppIsResumableFromNavigator(navigator);
            isWillSetIntoResumalbe = IsResumable;

        }

        private void GetMoreInfo(XPathNavigator navigator)
        {
            if (_author == null)
            {
                Author = GetAppAuthorFromNavigator(navigator);
            }

            if (_description == null)
            {
                Description = GetAppDesFromNavigator(navigator);
            }

            if (_subTitle == null)
            {
                SubTitle = GetAppSubTitleFromNavigator(navigator);
            }

            if (_bgIcon == null)
            {
                BgIcon = GetAppBgIconFromNavigator(navigator);
                BgIconPath = GetFileStreamFromXap(BgIcon);
            }

            if (willSetAppName == null)
            {
                willSetAppName = Name;
            }

            if (willSetAuthor == null)
            {
                willSetAuthor = Author;
            }

            if (willSetDes == null)
            {
                willSetDes = Description;
            }

            if (willSetSubTitle == null)
            {
                willSetSubTitle = SubTitle;
            }
        }

        #endregion

        #endregion

        public bool Modify()
        {
            //将xap的路径恢复为原目录
            RestoreFilePath();
            //设置Manifest临时目录
            string ManifestTempPath = ManifestPath + Name + Version + "\\";
            //将xap包中的Manifest解压到临时目录
            ExtractWMAppManifest(ManifestTempPath);

            XmlDocument document = ManifestHelper.GetDocumentFromPath(ManifestTempPath + WMAppManifest);

            if (isWillSetIntoResumalbe == true)
            {
                if (IsResumable!=true)
                {
                    MakeAppIntoResumable(document);
                    _isXapInfoUpdated = true;
                }
            }
            else
            {
                if (IsResumable!=false)
                {
                    DisMakeAppIntoResumable(document);
                    _isXapInfoUpdated = true;
                }
            }

            if (isWillSetIntoSettings == true)
            {
                if (IsInSetting!=true)
                {
                    MakeAppIntoSettings(document);
                    _isXapInfoUpdated = true;
                }
            }
            else
            {
                if (IsInSetting!=false)
                {
                    DisMakeAppIntoSettings(document);
                    _isXapInfoUpdated = true;
                }
            }

            if (IsInfoWillUpdate)
            {
                if (willSetSubTitle!=SubTitle)
                {
                    ChangeSubTitle(document, willSetSubTitle);
                    _isXapInfoUpdated = true;
                }

                if (willSetDes!=Description)
                {
                    ChangeDescription(document, willSetDes);
                    _isXapInfoUpdated = true;
                }

                if (willSetAppName!=Name)
                {
                    ChangeTitle(document, willSetAppName);
                    _isXapInfoUpdated = true;
                }

                if (willSetAuthor!=Author)
                {
                    ChangeAuthor(document, willSetAuthor);
                    _isXapInfoUpdated = true;
                }
            }
            
            if (_isXapInfoUpdated)
            {
                //保存修改后的Manifest
                document.Save(ManifestTempPath + WMAppManifest);
            }
            //将Manifest保存到Xap中
            SaveAsToXapFile(ManifestTempPath);
            //删除临时目录
            Directory.Delete(ManifestTempPath, true);
            return true;
        }

        #region Other

        public void RestoreFilePath()
        {
            FilePath = FilePath_bak;
        }

        private Stream GetFileStreamFromXap(string file)
        {
            MemoryStream rv = null;

            //在xml中，文件路径保存格式为 dir\file.ext 
            //而在zip.entry中，entry.FileName的格式为 dir/file.ext
            //所以要将file中的'\'替换为'/'
            file=file.Replace('\\', '/');

            ZipFile zip = new ZipFile(@FilePath, System.Text.Encoding.Default);
            List<ZipEntry> entries = new List<ZipEntry>();
            foreach (ZipEntry entry in zip)
            {
                string transedFileName = entry.FileName.Replace('\\', '/');
                if (transedFileName == file)
                {
                    rv = new MemoryStream(2048);
                    entry.Extract(rv);
                    rv.Seek(0, SeekOrigin.Begin);
                    break;
                }
            }
            zip.Save();
            return rv;
        }

        private void AddTopersistedAppData()
        {
            try
            {
                KnownApplication persistedAppData = new KnownApplication();
                if (!string.IsNullOrEmpty(Icon))
                {
                    using (Stream iconStream = GetFileStreamFromXap(Icon))
                    {
                        // save the image as the guid to maintain uniqueness
                        PersistedData.SaveBinaryStream(iconStream, Guid.ToString());
                    }

                    persistedAppData.Icon = Guid.ToString();
                }

                persistedAppData.Name = Name;

                // add this xap to persisted data
                PersistedData.Current.KnownApplication[Guid] = persistedAppData;
            }
            catch 
            {
                
            }
        }
       
        #endregion
    }
}
